# 1- 导包
from sklearn.datasets import load_iris
import seaborn as sns  # seaborn基于Matplotlib的一个画图工具
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.model_selection import train_test_split, GridSearchCV  # 数据集划分工具
from sklearn.preprocessing import StandardScaler  # 标准化处理
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score  # 准确度评估方法

"""
    交叉验证：只负责对样本数据中的训练集进行划分
    网格搜索：只负责从一堆参数中，选择最优的超参数
    它们两个一起使用
"""


def ywh_show_info():

    # 1.数据获取
    # 1.1 数据转换
    data = load_iris()
    data_list = data['data']      # 获得特征数据
    data_target = data['target']  # 获得目标值

    # 获取到数据类型和数值
    # print(data)
    # print(data_list,type(data_list))
    # print(data_target,type(data_target))

    # 将ndarray转换成df对象，以便后面绘图方便查看特征与目标值的关联性
    # df_x = pd.DataFrame(data_list, columns=data['feature_names'])
    # df_y = pd.DataFrame(data_target)
    # df_x['target'] = df_y
    # print(df_x)
    #
    # sns.lmplot(data=df_x, x="sepal length (cm)", y="petal width (cm)", hue="target", fit_reg=False)
    # plt.show()

    # 1.2 特征提取 划分数据集（训练特征数据、测试特征数据；训练目标数据、测试目标数据）
    x_train, x_test, y_train, y_test = train_test_split(data_list, data_target, test_size=0.2)

    # 2.特征工程
    """
           fit：模型只对数据进行学习，对数据不做处理
           transform：只对数据进行处理，不学习
           fit_transform：模型先对数据进行学习，然后对数据进行处理

           为什么训练集必须使用fit_transform，而测试集必须使用transform？
               1- 特征预处理需要从训练集中学习数据的信息，目前是要学习到均值、标准差
               2- 然后基于学到的规律对训练集进行数据处理
               3- 拿着从训练集中学到的数据信息，对测试集进行处理。如果也是用fit_transform，那么会学到测试集的信息，这个是不允许的
       """
    # 2.1 特征预处理
    data_Standard = StandardScaler()  # 实例化标准化对象

    # 2.2.标准化数据(标准化 训练集特征数据)
    new_x_data = data_Standard.fit_transform(x_train)   # 必须使用fit_transform（学习后转化数据）

    # 3.模型训练(分类算法)
    model = KNeighborsClassifier(n_neighbors=7)     # 这一步可以任意选一个参数、后面经过交叉验证和网格搜索之后得到最优超参数、再写进来

    # 3.1 创建交叉验证和网格搜索（为了得到更好的超参数K）
    # param_dict = {
    #     "n_neighbors":[i for i in range(1,11)]
    # }
    """
        参数解释
            estimator：初始化的算法模型
            param_grid：网格搜索的参数选择范围，类型是dict字典。key是超参数名称，value是超参数值的选择范围
            cv：交叉验证对训练集划分为多少份。一般为20以内
    """
    # 网格搜索
    # model = GridSearchCV(
    #     estimator=model,
    #     param_grid=param_dict,
    #     cv=5
    # )

    # 3.3- 将训练集的数据输入到交叉验证和网格搜索中
    # model.fit(new_x_data, y_train)
    #
    # # 3.4- 输出交叉验证和网格搜索的最优参数组合
    # print(model.cv_results_)
    # print(model.best_params_)
    # print(model.best_estimator_)
    # print(model.best_score_)
    # print(model.best_index_)

    model.fit(new_x_data, y_train)

    # 4.模型评估
    # 4.1使用测试集对模型进行评估
    new_x_test = data_Standard.transform(x_test)

    # 4.2 预测数据（使用测试数据对训练好的模型进行评估，进一步获取准确率）
    y_test_predict = model.predict(new_x_test)  # 使用测试数据对模型进行预测
    print(f"预测结果准确度是：{accuracy_score(y_test, y_test_predict)}")

    # 5.预测未知数据
    unkown_x = [[5, 6, 7, 6]]
    new_unkown_x = data_Standard.transform(unkown_x)  # 对新传入的数据进行标准化，统一单位
    result = model.predict(new_unkown_x)
    print(f"预测结果是花的分类：{result}")


if __name__ == '__main__':
    ywh_show_info()
